#include "coordTransformer.hpp"
#include "geometry.hpp"

namespace hms
{

CoordTransformer::CoordTransformer( Index size ){
	resize(size);
}

void CoordTransformer::resize( Index size ){
	m_size = size;
	m_transformBuf.resize( 2, size );
	m_cartesianBuf.resize( 2, size );
}

auto CoordTransformer::size() const -> Index {
	return m_size;
}

void CoordTransformer::checkSize() const {
	assert( size() > 0 );
	assert( m_transformBuf.cols() == size() );
	assert( m_cartesianBuf.cols() == size() );
}


auto CoordTransformer::transformed() const -> Array2Xs& {
	checkSize();
	return m_transformBuf;
}
auto CoordTransformer::cartesian() const -> Array2Xs& {
	checkSize();
	return m_cartesianBuf;
}

auto CoordTransformer::transform(
	const Ref<const Array2Xs>& cartesian,
	const Ref<const Array2Xs>& normals,
	bool orthoOnly
) const -> Array2Xs&
{
	checkSize();
	assert( size() == cartesian.cols() );
	assert( size() == normals.cols() );

	/* can still be optimised for orthogonal mesh types */
	if (orthoOnly){
		/* only the orthogonal component is needed */
		m_transformBuf.row(0) = ( normals * cartesian ).colwise().sum();
	} else {
		transformCoords( m_transformBuf, cartesian, normals );
	}
	return m_transformBuf;
}

auto CoordTransformer::retransform(
	const Ref<const Array2Xs>& transformed,
	const Ref<const Array2Xs>& normals
) const -> Array2Xs&
{
	checkSize();
	assert( size() == transformed.cols() );
	assert( size() == normals.cols() );

	retransformCoords( m_cartesianBuf, transformed, normals );
	return m_cartesianBuf;
}


using std::experimental::make_observer;

#define CHEAD(RETURN_TYPE) \
template<typename MeshType> \
RETURN_TYPE FieldTransformer<MeshType>::

CHEAD() FieldTransformer(
	const Field<MeshType>& f,
	const BoundaryPatch& p
) :
	m_transformer { p.size() }
{
	field(f);
	patch(p);
}

CHEAD(void) field( const Field<MeshType>& f ){
	assert( f.nComponents() == 2 ); // correct field passed? v makes most sense.
	m_field = make_observer<const Field<MeshType>>( &f );
}

CHEAD(void) patch( const BoundaryPatch& p ){
	m_patch = make_observer<const BoundaryPatch>( &p );
}


CHEAD(auto) field() const -> const Field<MeshType>& {
	assert( m_field );
	return *m_field;
}

CHEAD(auto) patch() const -> const BoundaryPatch& {
	assert( m_patch );
	return *m_patch;
}

	
CHEAD(auto) normals() const -> decltype(auto) {
	return patch().outwardNormals( field().mesh() );
}

CHEAD(auto) internal() const -> const CoordTransformer& {
	assert( m_transformer.size() == patch().size() );
	return m_transformer;
}

CHEAD(auto) transformed() const -> Array2Xs& {
	return internal().transformed();
}
CHEAD(auto) cartesian() const -> Array2Xs& {
	return internal().cartesian();
}

CHEAD(auto) transform( bool ghost, bool orthoOnly ) const -> Array2Xs& {
	if (ghost)
		return internal().transform(
			field(). ghostValues( patch() ), normals(), orthoOnly
		);
	else
		return internal().transform(
			field().domainValues( patch() ), normals(), orthoOnly
		);
}

CHEAD(auto) retransform( const Ref<const Array2Xs>& transformed ) const
	-> Array2Xs&
{
	return internal().retransform( transformed, normals() );
}

CHEAD(auto) retransform() const -> Array2Xs& {
	return retransform( internal().transformed() );
}


#undef CHEAD

#define INSTANTIATE_FOR_ALL_MESHTYPES(MeshType) \
template class FieldTransformer<MeshType>;
#include "meshInstantiator.hpp"

} // namespace hms
